

const playerStateIdle = 0;
const playerStateInitializing = 1;
const playerStatePlaying = 2;


function appendByteArray(buffer1, buffer2) {
    let tmp = new Uint8Array((buffer1.byteLength|0) + (buffer2.byteLength|0));
    tmp.set(buffer1, 0);
    tmp.set(buffer2, buffer1.byteLength|0);
    return tmp;
}
function Uint8ArrayToString(fileData){
  var dataString = "";
  for (var i = 0; i < fileData.length; i++) {
    dataString += String.fromCharCode(fileData[i]);
  }
 
  return dataString
}
function getVideoUnit(data) {
  let dv = new DataView(data.buffer);
  let index = 4;
  let hdrLeng = dv.getUint32(index, true); index += 4;
  let dataLeng = dv.getUint32(index, true); index += 4;
  let seq = dv.getUint32(index, true); index += 4;
  let sec = dv.getUint32(index, true); index += 4;
  let usec = dv.getUint32(index, true); index += 4;
  let checkSum = dv.getUint32(index, true); index += 4;
  let coding = dv.getUint16(index, true); index += 2;
  let fps = dv.getUint16(index, true); index += 2;
  let width = dv.getUint16(index, true); index += 2;
  let height = dv.getUint16(index, true); index += 2;
  let md = dv.getUint32(index, true); index += 4;
  let videodata = data.subarray(index);

/*
  console.log('video hdrLEng:' + hdrLeng + ' dataLeng:' + dataLeng + 
    ' seq:' + seq + ' sec:' + sec + ' usec:' + usec + 
    ' checkSum:' + checkSum + ' coding:' + coding + ' fps:' + fps + 
    ' width:' + width + ' height:' + height + ' md:' + md + ' datalen:' + videodata.length);
*/
  let timestamp = sec*1000 + usec/1000;
  // console.log('timestamp:' + timestamp);
  return {
    type: 'video',
    coding: coding,
    fps: fps,
    timestamp:timestamp,
    frameId: seq,
    recvTime: Date.now(), 
    data: videodata
  }
}
function getAudioUnit(data) {
  let dv = new DataView(data.buffer);
  let index = 4;
  let hdrLeng = dv.getUint32(index, true); index += 4;
  let dataLeng = dv.getUint32(index, true); index += 4;
  let seq = dv.getUint32(index, true); index += 4;
  let sec = dv.getUint32(index, true); index += 4;
  let usec = dv.getUint32(index, true); index += 4;
  let checkSum = dv.getUint32(index, true); index += 4;
  let format = dv.getUint16(index, true); index += 2;
  let channel = dv.getUint16(index, true); index += 2;
  let sampleRate = dv.getUint16(index, true); index += 2;
  let sampleBits = dv.getUint16(index, true); index += 2;
  let reserved = dv.getUint32(index, true); index += 4;
  let audiodata = data.subarray(index);

/*
  console.log('audio hdrLEng:' + hdrLeng + ' dataLeng:' + dataLeng + 
    ' seq:' + seq + ' sec:' + sec + ' usec:' + usec + 
    ' checkSum:' + checkSum + ' format:' + format + ' channel:' + channel + 
    ' sampleRate:' + sampleRate + ' sampleBits:' + sampleBits + ' reserved:' + reserved + 
    ' datalen:' + audiodata.length);
*/
  let timestamp = sec*1000 + usec/1000;
  // console.log('timestamp:' + timestamp);
  return {
    type: 'audio',
    timestamp: timestamp,
    recvTime: Date.now(), 
    data: audiodata
  }
}
function getUnit(event) {
    let data = new Uint8Array(event.data);
    let dv = new DataView(data.buffer);
    let sign = dv.getUint32(0, true);
    if(sign == 0xf5010000) { //video
      return getVideoUnit(data);
    } else if(sign == 0xf6010000) { //audio
      return getAudioUnit(data);
    } else {
      return null;
    }
}

function Player(canvas, url, id, profile)
{
    this.initData(url, id, profile);
    this.initDecodeWorker();
    this.canvas = canvas;
    this.webglPlayer = new WebGLPlayer(this.canvas, {preserveDrawingBuffer: false}); 
    this.animationFrameId = null;
    this.yuvdataQueue = [];
    this.monitor = {
        timer: null,
        frame: 0,
        frameAll: 0,
        count: 0
    };
}


Player.prototype =
{
    ws: null,
    destroy: function () {
        this.uninitWebsocket();
        this.uninitMonitorTimer();
        this.uninitDecodeWorker();
        this.uninitDisplayLoop();
        this.setState(playerStateIdle);
    },
    onDecoderMessage: function (evt) {
        var objData = evt.data;
        switch (objData.t) {
            case kOpenDecoderRsp:
                if(objData.e === 0) { 
                    console.log("kOpenDecoderRsp success");
                    this.initWebsocket();
                } else {
                    console.log("kOpenDecoderRsp error");
                }
                break;
            case kVideoFrame:
                this.yuvdataQueue.push(objData);
                break;
        }
    },
    initDecodeWorker: function () {
      this.decodeWorker = new Worker("decoder.js");
      this.decodeWorker.onmessage = this.onDecoderMessage.bind(this);
    },
    uninitDecodeWorker: function () {
      if(this.decodeWorker) {
        this.decodeWorker.postMessage({t: kCloseDecoderReq});   
        this.decodeWorker.terminate();
        this.decodeWorker = null;
      }
    },
    initData: function(url, id, profile) {
        this.url = url;
        this.profile = profile;
        this.id = id;
        this.debug = false;
        this.mode = 'both';   

        this.state = playerStateIdle; 
        this.auth = null;
    },    
    setAuth: function(auth) {
        this.auth = auth;
    },
    setDebug: function(debug) {
        this.debug = debug;
    }, 
    setMode: function(mode) {
        this.mode = mode;
    },     
    reset: function(id, profile) {
      console.log("reset:" + profile);
      this.initData(this.url, id, profile);
    },
    onready: function() {
      console.log('onReady');
      this.setState(playerStatePlaying);
    }, 
    handleUnit: function(unit) {
      // console.log(unit);
        if(unit.type == 'video') { //video
            let msg = {t: kFeedDataReq, d: unit.data, coding: unit.coding};
            this.decodeWorker.postMessage(msg, [msg.d.buffer]);        
        }
    },
    handleCmd: function (cmdString) {
      let cmd = JSON.parse(cmdString);
      console.log(cmd);
      if(cmd.cmd == "option") {
        if(cmd.audio == 0) {
          this.mode = 'video';
        }
      }
    },
    uninitWebsocket: function () {
        if(this.ws) {
            this.ws.close();
            this.ws = null;
        }
    },
    initWebsocket: function () {
      let wsurl = this.url + '?profile=' + this.profile;
      console.log('websocket url:' + wsurl);
      if(this.auth && typeof this.auth === 'string' && this.auth.length > 0) {
        this.ws = new WebSocket(wsurl, this.auth);
      } else {
        this.ws = new WebSocket(wsurl);
      }
      this.ws.binaryType = 'arraybuffer';  
      this.ws.addEventListener('open', function() {
            console.log("websocket onopen:" + this.profile);
            this.ws.addEventListener('message', function(event) {
                if(typeof event.data === 'string') {
                    this.handleCmd(event.data);
                } else {
                    var unit = getUnit(event);
                    this.handleUnit(unit);
                }
            }.bind(this));
            this.ws.addEventListener('error', function(event) {
                console.log("websocket error");
                console.log(event);
            });
            this.ws.addEventListener('close', function(e) {
                console.log('websocket 断开: ' + e.code + ' ' + e.reason + ' ' + e.wasClean);
                console.log(event);
            });
            this.setState(playerStatePlaying);
      }.bind(this));           
    },
    initMonitorTimer: function() {
        this.uninitMonitorTimer();
        this.monitor.timer = setInterval(function () {
            this.monitor.frameAll += this.monitor.frame;
            this.monitor.count += 1;
            console.log("fps:" + this.monitor.frame + 
                ' aver:' + (this.monitor.frameAll / this.monitor.count) +
                " queue:" + this.yuvdataQueue.length);
            this.monitor.frame = 0;
          }.bind(this), 1000);
    },
    uninitMonitorTimer: function () {
        if(this.monitor.timer) {
            clearInterval(this.monitor.timer);
            this.monitor.timer = null;
        }
    },
    play: function() {
        this.setState(playerStateInitializing);
        this.decodeWorker.postMessage({t:kOpenDecoderReq});  
        this.initDisplayLoop();
        this.initMonitorTimer();
    },
    uninitDisplayLoop: function() {
        if(this.animationFrameId) {
            cancelAnimationFrame(this.animationFrameId);
            this.animationFrameId = null;
        }
    },
    initDisplayLoop: function() {
        this.animationFrameId = requestAnimationFrame(this.initDisplayLoop.bind(this));
        if (this.yuvdataQueue.length == 0) {
            return;
        }
        for (i = 0; i < 2; ++i) {
            var objData = this.yuvdataQueue[0];
            var data = new Uint8Array(objData.d);
            var width = objData.w;
            var height = objData.h;
            var yLength = width * height;
            var uvLength = (width / 2) * (height / 2);                  
            this.webglPlayer.renderFrame(data, width, height, yLength, uvLength);

            this.yuvdataQueue.shift();
            this.monitor.frame++;
            if (this.yuvdataQueue.length == 0) {
                break;
            }
        }
    },
    setState: function(state) { //idle init playing stop 
      console.log('set state:' + state);
      this.state = state;
    },
    getState: function() {
      return this.state;
    }
}

